package com.autonavi.yunda.yunji.core.engine.scriptresolver;

import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.core.engine.exception.EnginePreconditions;
import com.autonavi.yunda.yunji.core.engine.handler.HttpHandler;
import com.autonavi.yunda.yunji.core.engine.script.GroovyFunctionScriptClass;
import com.autonavi.yunda.yunji.core.engine.script.IFunctionScript;
import com.autonavi.yunda.yunji.core.engine.script.JsFunctionScript;
import com.autonavi.yunda.yunji.core.enums.DatasourceType;
import com.autonavi.yunda.yunji.core.enums.EdgeType;
import com.autonavi.yunda.yunji.core.enums.Method;
import com.autonavi.yunda.yunji.core.enums.NodeType;
import com.autonavi.yunda.yunji.core.utils.ValidUtil;
import com.autonavi.yunda.yunji.core.vo.EngineData;
import com.autonavi.yunda.yunji.core.vo.node.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

import static com.autonavi.yunda.yunji.core.config.EngineConstant.*;

/**
 * @author cizhong.wcs
 * {@code @date} 2022/5/18 16:30
 */
@Slf4j
public class ScriptResolver {
    private static final Logger SCRIPT_LOGGER = LoggerFactory.getLogger("SCRIPT_LOG");
    private static final Set<String> DEFAULT_IMPORTS = Set.of(
            LoggerFactory.class.getName(),
            Logger.class.getName(),
            EnginePreconditions.class.getName(),
            StringBuilder.class.getName()
    ).stream().map(it -> "import " + it).collect(Collectors.toSet());

    /**
     * 解析接口主脚本
     */
    public static String resolveInterfaceScript(EngineData engineData, String projectId, String responseFormat) {
        GroovyScriptWriter gsw = new GroovyScriptWriter();
        writeCommonImport(gsw);
        String className = "Script_" + System.currentTimeMillis() + "_" + Math.abs(gsw.hashCode());
        gsw.println("class %s {", className);
        writeLogger(className, gsw);
        //写入主脚本所有用到的变量(包括入参和各个节点的输出映射)
        writeAllVariables(engineData, gsw);
        //写入入参校验方法
        writeParamValidate(engineData, gsw);
        //写入主脚本所有'节点'的参数映射方法
        engineData.nodes.forEach(node -> writeInterfaceNodeFunction(node, gsw, projectId));
        //写入主脚本所有'边'的参数映射方法
        engineData.edges.forEach(edge -> writeInterfaceNodeFunction(edge, gsw, projectId));
        //写入输出结果方法(data)
        writeOutputParams(engineData, gsw);
        //写入返回结果方法(response)
        writeResponse(responseFormat, gsw);
        //写入traceLog方法
        writeTraceLogParams(engineData, gsw);
        //写入responseHeader方法
        writeResponseHeader(engineData, gsw);
        gsw.println("}");
        return gsw.toString();
    }

    /**
     * 解析节点和边中条件单独的脚本
     */
    public static Map<String, IFunctionScript> resolveNodesToFunctionScriptMap(EngineData engineData) {
        Map<String, IFunctionScript> nameScriptMap = new HashMap<>(8);
        // 节点函数
        engineData.nodes.forEach(node -> {
            if (!node.nodeType.isSpecial()) {
                nameScriptMap.put(node.name, resolveNode(node));
            }
        });
        // 连接线函数
        engineData.edges.forEach(edgeNode -> {
            if (EdgeType.custom.equals(edgeNode.type)) {
                nameScriptMap.put(edgeNode.getName(), resolveNode(edgeNode));
            }
        });
        return nameScriptMap;
    }

    /**
     * 根据节点类型生成合适的IFunctionScript对象（策略模式）
     * @param node
     * @return
     */
    public static IFunctionScript resolveNode(BaseNode node) {
        switch (node.getNodeType()) {
            case groovy:
                return resolveGroovyScript(((GroovyFunctionNode) node));
            case javascript:
                return resolveJsNode((JsFunctionNode) node);
            default:
                return resolveOtherNode(node);
        }
    }

    /**
     * 写入主脚本节点和边的参数映射方法
     */
    private static void writeInterfaceNodeFunction(BaseNode node, GroovyScriptWriter gsw, String projectId) {
        if (node.getNodeType().isSpecial() || (node.getNodeType().equals(NodeType.edge) && !EdgeType.custom.equals(((EdgeNode) node).type))) {
            return;
        }
        gsw.tab(1).println("def %s() {", node.getName());
        gsw.tab(2).println("[");
        node.params.forEach(nodeParam -> {
            String paramRef = nodeParam.dataSource;//数据来源
            //如果是常量
            if (CONSTANT_VALUE_REF.equals(paramRef)) {
                String constantValue = nodeParam.debugValue != null ? "" + nodeParam.debugValue : "";
                paramRef = String.format("ParamResolver.getParamValueByType(  \"\"\"%s\"\"\", \"\"\"%s\"\"\")", constantValue.replaceAll("\\\\", "\\\\\\\\"), nodeParam.type);
            } else if (DYNAMIC_CONFIG_REF.equals(paramRef)) {
                paramRef = String.format("ParamResolver.getParamValueByType(DynamicConfigUtil.getString( \"\"\"%s\"\"\", \"\"\"%s\"\"\"), \"\"\"%s\"\"\")", projectId, nodeParam.debugValue, nodeParam.type);
            }
            gsw.tab(4).println("\"%s\": %s,", nodeParam.getParamNameInScript(), getParaValueReferenceInScript(paramRef));
        });
        if (node.params.isEmpty()) {
            gsw.println(":");
        }
        gsw.tab(2).println("]");
        gsw.tab(1).println("}");
        gsw.println();
    }

    private static String getParaValueReferenceInScript(String paramRef) {
        String var = paramRef.split("\\.")[0].replaceAll("\\[.*", "");
        //符合参数规范则不调整
        if (ValidUtil.checkRegexAndReturn(var, GROOVY_SYNTAX_VARIABLE_REGEX)) {
            return paramRef;
        } else {
            var = var.replace("-", "$");
            String[] vars = paramRef.split("\\.");
            vars[0] = var;
            return String.join(".", vars);
        }
    }


    private static IFunctionScript resolveJsNode(JsFunctionNode node) {
        return new JsFunctionScript(node);
    }


    private static IFunctionScript resolveGroovyScript(GroovyFunctionNode groovyFunctionNode) {
        GroovyScriptWriter gsw = new GroovyScriptWriter();
        try {
            // 创建CodeVisitor对象
            CodeVisitor visitor = CodeVisitor.toCodeVisitor(groovyFunctionNode.script);
            // gsw写入需要导入的数据
            LinkedHashSet<String> allImports = visitor.allImports;
            allImports.addAll(DEFAULT_IMPORTS);
            allImports.forEach(name -> gsw.tab(0).println(name));
            // 写入文件名
            String className = "Script_" + System.currentTimeMillis() + "_" + Math.abs(groovyFunctionNode.hashCode());
            gsw.tab(0).println("class " + className + " {");
            // 写入日志对象
            gsw.tab(2).println("Logger log = LoggerFactory.getLogger(\"" + className + "\");");
            // 如果是代码调试，写入__singleDebugLog
            if (groovyFunctionNode.singleNodeDebug) {
                gsw.tab(2).println("def " + DEBUG_LOG_PARAM_NAME + " = new StringBuilder();");
            }
            groovyFunctionNode.params.forEach(param -> gsw.tab(2).println("def " + param.getParamNameInScript()));
            //写入方法
            visitor.getMethods().entrySet().stream()
                    .filter(pair -> !"main".equals(pair.getKey()) && !EXEC_LOG_DEBUG_INFO_PROCESS.equals(pair.getKey()))
                    .forEach(pair -> gsw.tab(2).println(pair.getValue()));
            // 写入结果校验方法
            writeCommonValidResult(gsw, groovyFunctionNode);
            // 写入debug节点调试日志方法
            writeDebugLoggerMethod(gsw, groovyFunctionNode);
            //class content end
            gsw.tab(0).println("}");
        } catch (AmapException e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "script解析错误", e);
        } catch (Exception e) {
            log.info("script error: {}", gsw, e);
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "script解析错误", e);
        }
        SCRIPT_LOGGER.debug("name = {},groovy function script :\n=========\n{}\n=========", groovyFunctionNode.getName(), gsw);
        return new GroovyFunctionScriptClass(gsw.toString(), groovyFunctionNode.getName());
    }

    private static void writeDebugLoggerMethod(GroovyScriptWriter gsw, GroovyFunctionNode node) {
        gsw.tab(1).println("def %s(__debug_info){", EXEC_LOG_DEBUG_INFO_PROCESS);
        if (node.singleNodeDebug) {
            gsw.tab(2).println(DEBUG_LOG_PARAM_NAME + ".append(__debug_info).append(\"\\n\")");
        }
        gsw.tab(1).println("}");
    }

    /**
     * 解析其他类型节点成IFunctionScript对象
     * 模版+策略模式
     * @param node
     * @return
     */
    private static IFunctionScript resolveOtherNode(BaseNode node) {
        GroovyScriptWriter gsw = new GroovyScriptWriter();
        // 临时类名 Script + 当前时间戳 + 当前脚本写类哈希值
        String className = "Script_" + System.currentTimeMillis() + "_" + Math.abs(gsw.hashCode());
        // 导入基本类库
        writeCommonImport(gsw);
        gsw.println("class %s {", className);
        writeNodeVariables(node, gsw);
        gsw.tab(1).println("def process() {");
        switch (node.getNodeType()) {
            case sql:
            case yundaSQLData:
            case yundaTemplateSQLData:
                writeSqlNode((SqlNode) node, gsw);
                break;
            case http:
            case yundaAPI:
                writeHttpNode((HttpNode) node, gsw);
                break;
            case transform:
                writeGroovyTransformNode((TransformNode) node, gsw);
                break;
            case redis:
                writeRedisNode((RedisNode) node, gsw);
                break;
            case edge:
                writeEdgeNode((EdgeNode) node, gsw);
                break;
            default:
                throw new AmapException(AmapExceptionCode.COMMON_ILLEGAL_PARAM, "异常节点类型 :" + node.getNodeType());
        }
        gsw.tab(1).println("}");
        writeCommonValidResult(gsw, node);
        gsw.println("}");
        SCRIPT_LOGGER.debug("node other groovy script name = {} script :\n=========\n{}\n=========", node.getName(), gsw);
        return new GroovyFunctionScriptClass(gsw.toString(), node.name);
    }

    private static void writeGroovyTransformNode(TransformNode node, GroovyScriptWriter gsw) {
        gsw.tab(2).println("FunctionScriptClassManager.getFunctionScript(\"%s\",\"%s\").exeFun(%s,\"%s\")",
                node.functionName, node.functionVersion, SCRIPT_PARAMS_MAP_NAME, EXEC_PROCESS);
    }

    private static void writeCommonValidResult(GroovyScriptWriter gsw, BaseNode node) {
        if (Objects.isNull(node.resultValidators) || node.resultValidators.isEmpty()) {
            return;
        }
        gsw.tab(1).println("def %s(result){", EXEC_VALIDATOR_RESULT_PROCESS);
        node.resultValidators.forEach(resultValidParam -> {
            EnginePreconditions.check(StringUtils.isNotBlank(resultValidParam.expression), node.var + "结果校验表达式为空");
            gsw.tab(2).println("EnginePreconditions.check({");
            gsw.tab(2).println(resultValidParam.expression);
            gsw.tab(2).println("}.call(), \"\"\"%s\"\"\", \"\"\"%s\"\"\")", resultValidParam.code, resultValidParam.message);
        });
        gsw.tab(1).println("}");
    }

    private static void writeCommonImport(GroovyScriptWriter gsw) {
        gsw
                //静态引入
                .printlnStaticImport("com.autonavi.yunda.yunji.core.utils.ValidUtil.*")
                .printlnStaticImport("com.autonavi.yunda.yunji.core.utils.DynamicSqlUtil.*")
                //非静态引入
                .printlnImport("com.autonavi.yunda.yunji.core.engine.handler.RedisHandler")
                .printlnImport("com.autonavi.yunda.yunji.core.engine.handler.OssHandler")
                .printlnImport("com.autonavi.yunda.yunji.core.engine.handler.SqlHandler")
                .printlnImport("com.autonavi.yunda.yunji.core.engine.handler.HttpHandler")
                .printlnImport("com.autonavi.yunda.yunji.common.utils.JsonUtils")
                .printlnImport("com.autonavi.yunda.yunji.core.utils.DynamicConfigUtil")
                .printlnImport("com.autonavi.yunda.yunji.core.engine.argresolver.ParamResolver")
                .printlnImport("com.autonavi.yunda.yunji.core.engine.exception.EnginePreconditions")
                .printlnImport("java.util.stream.Collectors")
                .printlnImport("org.slf4j.Logger")
                .printlnImport("org.slf4j.LoggerFactory")
                .printlnImport("org.apache.commons.lang3.StringUtils")
                .printlnImport("com.autonavi.yunda.yunji.core.engine.script.FunctionScriptClassManager")
                .printlnImport("com.autonavi.yunda.yunji.core.utils.datahub.PropertiesTranslateUtils");


    }

    private static void writeNodeVariables(BaseNode baseNode, GroovyScriptWriter gsw) {
        baseNode.params.forEach(nodeParam ->
                gsw.tab(1).println("def %s", nodeParam.getParamNameInScript())
        );
        // Transform节点特殊处理
        if (baseNode.getNodeType().equals(NodeType.transform)) {
            gsw.tab(1).println("def %s", SCRIPT_PARAMS_MAP_NAME);
        }
        gsw.println();
    }

    /**
     * 写入变量： 1. 固定变量 __context 2. 用户传入的变量
     */
    private static void writeAllVariables(EngineData engineData, GroovyScriptWriter gsw) {
        gsw.tab(1).println("def %s", CONTEXT_PARAM_NAME);
        engineData.engineConfig.initParams.forEach(nodeParam ->
                gsw.tab(1).println("def %s = %s", nodeParam.name, String.format(
                        "ParamResolver.getParamValueByType(  \"\"\"%s\"\"\", \"\"\"%s\"\"\")",
                        (nodeParam.debugValue + "").replaceAll("\\\\", "\\\\\\\\"), nodeParam.type))
        );
        engineData.fetchAllGlobalVariables().forEach(var ->
                gsw.tab(1).println("def %s", var)
        );
        gsw.println();
    }

    private static void writeLogger(String className, GroovyScriptWriter gsw) {
        gsw.tab(1).println("Logger log = LoggerFactory.getLogger(\"%s\")", className);
        gsw.println();
    }

    /**
     * 写入参数校验方法
     */
    private static void writeParamValidate(EngineData engineData, GroovyScriptWriter gsw) {
        StartNode startNode = engineData.fetchStartNode();
        gsw.tab(1).println("def %s() {", PARAM_VALIDATE_FUN_NAME);
        startNode.paramValidators.forEach(paramValidator -> {
            gsw.tab(2).println("EnginePreconditions.check({");
            gsw.tab(4).println(paramValidator.expression);
            gsw.tab(2).println("}.call(), \"\"\"%s\"\"\", \"\"\"%s\"\"\")", paramValidator.code, paramValidator.message);
        });
        gsw.tab(1).println("}").println();
    }

    private static void writeOutputParams(EngineData engineData, GroovyScriptWriter gsw) {
        gsw.tab(1).println("def %s() {", OUTPUT_FUN_NAME);
        if (!engineData.fetchEndNode().outputParams.isEmpty()) {
            gsw.tab(2).println("[");
            engineData.fetchEndNode().outputParams.forEach((k, v) -> gsw.tab(4).println("\"\"\"%s\"\"\": %s,", k, v));
            gsw.tab(2).println("]");
        } else {
            gsw.tab(2).println("[%s:null]", ENGINE_REAL_OUTPUT);
        }
        gsw.tab(1).println("}").println();
    }

    private static void writeTraceLogParams(EngineData engineData, GroovyScriptWriter gsw) {
        gsw.tab(1).println("def %s() {", TRACE_LOG_FUN_NAME);
        if (!engineData.fetchEndNode().traceLogInfo.isEmpty()) {
            gsw.tab(2).println("[");
            engineData.fetchEndNode().traceLogInfo.forEach((k, v) ->
                    gsw.tab(4).println("\"\"\"%s\"\"\": %s,", k, StringUtils.isNotBlank(v) ? v : null)
            );
            gsw.tab(2).println("]");
        } else {
            gsw.tab(2).println("[:]");
        }
        gsw.tab(1).println("}").println();
    }

    private static void writeResponseHeader(EngineData engineData, GroovyScriptWriter gsw) {
        gsw.tab(1).println("def %s() {", RESPONSE_HEADERS_FUN_NAME);
        if (!engineData.fetchEndNode().responseHeaders.isEmpty()) {
            gsw.tab(2).println("[");
            engineData.fetchEndNode().responseHeaders.forEach((k, v) ->
                    gsw.tab(4).println("\"\"\"%s\"\"\": %s,", k, StringUtils.isNotBlank(v) ? v : null)
            );
            gsw.tab(2).println("]");
        } else {
            gsw.tab(2).println("[:]");
        }
        gsw.tab(1).println("}").println();
    }


    private static void writeResponse(String responseFormat, GroovyScriptWriter gsw) {
        gsw.tab(1).println("def %s(__success, __code, __message, __traceId, __data) {", RESPONSE_FUN_NAME);
        if (StringUtils.isNotBlank(responseFormat)) {
            gsw.tab(2).println(responseFormat);
        } else {
            gsw.tab(2).println("[\n" +
                    "                \"success\": __success,\n" +
                    "                \"code\"   : __code,\n" +
                    "                \"message\": __message,\n" +
                    "                \"traceId\": __traceId,\n" +
                    "                \"data\"   : __data\n" +
                    "        ]");
        }
        gsw.tab(1).println("}").println();
    }


    private static void writeEdgeNode(EdgeNode node, GroovyScriptWriter gsw) {
        if (EdgeType.custom.equals(node.type)) {
            gsw.tab(2).println(node.script);
        }
    }

    private static void writeSqlNode(SqlNode node, GroovyScriptWriter gsw) {
        gsw.tab(2).println("SqlHandler.sql(");
        gsw.tab(4).println("\"\"\"%s\"\"\",", node.dbName);
        gsw.tab(4).println("\"\"\"%s\"\"\",", node.sql);
        gsw.tab(4).println("[");
        gsw.tab(6).printlnString("\"\"\"%s\"\"\" : %s,", "camel", node.camel);
        gsw.tab(6).printlnString("\"\"\"%s\"\"\" : \"%s\",", "sqlResultType", node.sqlResultType);
        gsw.tab(6).printlnString("\"\"\"%s\"\"\" : \"%s\",", "sqlDebugType", node.debug);
        gsw.tab(4).println("]");
        gsw.tab(2).println(")");
    }

    /**
     * 写入http节点独有的代码
     * @param node
     * @param gsw
     */
    private static void writeHttpNode(HttpNode node, GroovyScriptWriter gsw) {
        gsw.tab(2).println("HttpHandler.http(");
        gsw.tab(4).println("\"\"\"%s\"\"\",", node.url);
        gsw.tab(4).println("\"\"\"%s\"\"\",", node.method);
        //兼容旧的数据,没有paramGroup
        node.params.stream().filter(p -> p.paramGroup == null)
                .forEach(p -> p.paramGroup = node.method.equals(Method.GET) ? "query" : "body");
        // 传入param参数
        writeHttpNodeParams(node, gsw, "query");
        // 传入请求体
        writeHttpNodeParams(node, gsw, "body");
        // 传入请求头
        writeHttpNodeParams(node, gsw, "headers");
        // 传入配置信息
        gsw.tab(4).println("[");
        gsw.tab(6).printlnString("\"\"\"%s\"\"\" : %s,", "isVipServer", node.isVipServer);
        gsw.tab(6).printlnString("\"\"\"%s\"\"\" : %s,", "retryTimes", node.retryTimes);
        gsw.tab(6).printlnString("\"\"\"%s\"\"\" : \"\"\"%s\"\"\",", "contentType", node.contentType);
        gsw.tab(6).printlnString("\"\"\"%s\"\"\" : %s,", "timeout", node.timeout);
        gsw.tab(6).printlnString("\"\"\"%s\"\"\" : %s,", "useHttp_1", node.useHttp_1);
        gsw.tab(4).println("],");
        gsw.tab(2).println(")");
    }

    private static void writeHttpNodeParams(HttpNode node, GroovyScriptWriter gsw, String paramGroup) {
        if (node.params.stream().anyMatch(p -> paramGroup.equals(p.paramGroup))) {
            gsw.tab(4).println("[");
            node.params.stream()
                    .filter(p -> paramGroup.equals(p.paramGroup))
                    .forEach(nodeParam -> gsw.tab(6).printlnString("\"%s\" : %s,", nodeParam.name, nodeParam.getParamNameInScript()));
            gsw.tab(4).println("],");
        } else {
            gsw.tab(4).println("[:],");
        }
    }


    /**
     * 生成redis节点-groovy脚本
     *
     * @param node redis节点
     * @param gsw  groovy生成代码
     */
    private static void writeRedisNode(RedisNode node, GroovyScriptWriter gsw) {
        gsw.tab(2).println((DatasourceType.REDIS.toString().equals(node.cacheType.toUpperCase())) ? "RedisHandler.redis(" : "OssHandler.oss(");
        gsw.tab(4).println("\"\"\"%s\"\"\",", node.redisInstance);
        gsw.tab(4).printlnString("\"\"\"%s\"\"\",", node.operate);
        gsw.tab(4).printlnString("\"\"\"%s\"\"\",", node.key);
        if ("uploadFile".equals(node.operate)) {
            gsw.tab(4).printlnString("%s,", node.value);
        } else {
            gsw.tab(4).printlnString("\"\"\"%s\"\"\",", node.value);
        }
        if (DatasourceType.REDIS.toString().equals(node.cacheType.toUpperCase())) {
            gsw.tab(4).printlnString("\"\"\"%s\"\"\",", node.expire);
        } else {
            gsw.tab(4).printlnString("\"\"\"%s\"\"\",", node.ossPath);
        }
        gsw.tab(2).println(")");
    }


}
